Expand description

The heart of this crate is ErrorStack, its a derive macro to make generating enums and structs compatible with error_stack. Even though the sole purpose is provind a better DX with error_stack this derive macro can actually be used with any other error system since the crate itself doesn’t depend on error_stack all it does is makes std::error::Error and std::fmt::Display implementations easy.

Usage with and without error_stack

With

Note: As of right-now no-std is not supported

With error_stack you get the Report and their fancy attachments, context, frames, etc. features, which to say the least are pretty cool and helpful for error handling & debugging.

use error_stack::{IntoReport, Result, ResultExt};
use error_stack_derive::ErrorStack;

#[derive(ErrorStack, Debug)]
#[error_message("An exception occured in foo")]
struct FooError;

fn main() -> Result<(), FooError> {
    let contents = std::fs::read_to_string("foo.txt")
        .report()
        .change_context(FooError)
        .attach_printable("Unable to read foo.txt file")?;

    println!("{contents}");

    Ok(())
}

Without

Ofcourse this crate doesn’t enforce the usage of error_stack infact you can use it with any other error handling crate, just like this

use error_stack_derive::ErrorStack;

#[derive(ErrorStack, Debug)]
#[error_message(&format!("An exception occured with foo: {}", self.0))]

struct FooError(String);
fn main() -> Result<(), FooError> {
    let contents = std::fs::read_to_string("foo.txt").map_err(|e| FooError(e.to_string()))?;

    println!("{contents}");

    Ok(())
}

Looking into the expansion

This crate, specifically the derive macro, does 2 things,
one, implements std::error::Error
two, implements std::fmt::Display
you can derive a struct or an enum, the trait impl are pretty simple

For a struct:

// #[derive(error_stack_derive::ErrorStack, Debug)]
// #[error_message("An exception occured in foo")]
// struct FooError;

#[derive(Debug)]
struct FooError;

impl std::fmt::Display for FooError {
    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        fmt.write_str("An exception occured in foo")
    }
}

impl std::error::Error for FooError {}

For an enum:

// #[derive(error_stack_derive::ErrorStack, Debug)]
// enum FooErrors {
//  #[error_message("An exception in bar")]
//  BarError,
//  #[error_message(&format!("Error in baz ({unnamed0})"))]
//  BazError(String),
//  #[error_message(&format!("Error in qux ({start}, {end})"))]
//  QuxError {
//      start: u64,
//      end: u64,
//  }
// };

#[derive(Debug)]
enum FooErrors {
 BarError,
 BazError(String),
 QuxError {
     start: u64,
     end: u64,
 }
};

impl std::fmt::Display for FooErrors {
    fn fmt(&self, _____fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::BarError => _____fmt.write_str(&format!("[{name}] An error occured; {:?}", name = "FooErrors", self)),
            Self::BazError(unnamed0) => _____fmt.write_str(&format!("Error in baz ({unnamed0})")),
            Self::QuxError { start, end } => _____fmt.write_str(&format!("Error in qux ({start}, {end})")),
        }
    }
}

impl std::error::Error for FooErrors {}

As you can see its a pretty simple macro but definitely helps when you have a large code base and error handling definitely becomes dreadful. Read up the doc comments of ErrorStack for more information.

Derive Macros

A derive-macro to easily create enums and structs compatible with error_stack. You can use a struct or an enum with it